package com.naiterui.ehp.bs.esign.api.wukong.helper;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;

import com.alibaba.fastjson.JSONObject;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.EsignServiceProviderEnum;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bs.esign.api.vo.SignAccountVO;
import com.naiterui.ehp.bs.esign.api.vo.SignFileParamVO;
import com.naiterui.ehp.bs.esign.api.wukong.config.ESignProperties;
import com.naiterui.ehp.bs.esign.api.wukong.vo.RealnameCodeResultVO;
import com.naiterui.ehp.bs.esign.api.wukong.vo.SignAccessTokenVO;
import com.naiterui.ehp.bs.esign.api.wukong.vo.SignFaceResultVO;
import com.naiterui.ehp.bs.esign.api.wukong.vo.SignResponseVO;
import com.naiterui.ehp.bs.esign.exception.ExceptionCodes;
import com.naiterui.ehp.bs.esign.utils.Constants;
import com.naiterui.ehp.bs.esign.utils.EsignWillTypeEnum;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description 人脸识别意愿签名辅助类
 * @Author gongbaoqiang
 * @Date 2019/12/25 2:26 PM
 */
@Slf4j
public class OpenApiHelper {

    /**
     * accessToken缓存键
     */
    private final static String RDS_ACCESS_TOKEN_CACHE = CommonConstant.REDIS_PRE_ESIGN + "esign_accessToken";


    /**
     * 获取accessToken
     *
     * @return
     *
     * @throws BusinessException
     */
    public String accessToken() throws BusinessException {
        String accessToken = RedisUtil.valueOps().getString(RDS_ACCESS_TOKEN_CACHE);
        log.info("获取accessToken:{}", accessToken);
        if (null == accessToken) {
            accessToken = this.refreshAccessToken();
        }
        return accessToken;
    }

    /**
     * 刷新accessToken
     *
     * @return
     *
     * @throws BusinessException
     */
    public String refreshAccessToken() throws BusinessException {
        Map<String, String> paramMap = new HashMap<>(2);
        paramMap.put("appId", ESignProperties.PROJECT_ID);
        paramMap.put("secret", ESignProperties.PROJECT_SECRET);
        paramMap.put("grantType", "client_credentials");
        log.info("刷新accessToken: param={}, url={}", paramMap, ESignProperties.ACCESS_TOKEN_API);

        ResponseEntity<SignResponseVO<SignAccessTokenVO>> responseEntity =
                RestHttpUtil.get()
                            .url(ESignProperties.ACCESS_TOKEN_API)
                            .paramMap(paramMap)
                            .execute(new ParameterizedTypeReference<SignResponseVO<SignAccessTokenVO>>() {
                            });

        SignResponseVO<SignAccessTokenVO> accessTokenResult = responseEntity.getBody();
        log.info("body={}", responseEntity.toString());
        if (accessTokenResult == null) {
            log.warn("AccessToken失败，结果：{}", accessTokenResult);
            throw new BusinessException(ExceptionCodes.ESIGN_AUTH_REQUEST_FAILED, "AccessToken失败:" + accessTokenResult.getMessage());
        }

        SignAccessTokenVO data = accessTokenResult.getData();
        String accessToken = data.getToken();
        long expireIn = new BigDecimal(data.getExpiresIn()).longValue() - System.currentTimeMillis();
        int expireSecond = new BigDecimal(expireIn).divide(new BigDecimal(1000)).intValue();

        RedisUtil.valueOps().setString(RDS_ACCESS_TOKEN_CACHE, accessToken, expireSecond - 120);

        //        Date refreshTime = DateUtil.addSeconds(new Date(), expireSecond - 120);
        //        String refreshTimeStr = DateUtil.formatDate(refreshTime, DateUtil.CRON_TIME_FORMAT);
        //        try {
        //            //先删除未执行的刷新任务
        //            ScheduleUtils.removeCustomerTask(ConfigUtil.getString(Constants.TASK_SCHEDULE_HOST), ScheduleUtils
        //            .TASK_TYPE_ESIGN_ACCESS_TOKEN, APP_ID);
        //            //添加新的刷新任务
        //            ScheduleUtils.addCustomerTask(ConfigUtil.getString(Constants.TASK_SCHEDULE_HOST), refreshTimeStr,
        //                    ScheduleUtils.TASK_TYPE_ESIGN_ACCESS_TOKEN, APP_ID, ACCESS_TOKEN_REFRESH_URL, scheduleParam);
        //        } catch (Exception e) {
        //            log.error("accessToken刷新任务添加失败，errmsg={}", e);
        //        }

        return accessToken;
    }


    /**
     * <pre>
     *     意向签署地址：人脸识别
     * </pre>
     *
     * @param signAccountVO
     *
     * @return
     *
     * @throws BusinessException
     */
    public SignFaceResultVO getFaceUrl(SignFileParamVO signParamVO, SignAccountVO signAccountVO)
        throws BusinessException {
        Map<String, String> header = this.getHeaderParam();

        Map<String, Object> param = new HashMap<>();
        param.put("mobile", signAccountVO.getPhone());
        param.put("idNumber", signAccountVO.getIdNo());
        param.put("name", signAccountVO.getName());
        //        param.put("noticeDeveloperUrl", properties.getFace().getNoticeDeveloperUrl());
        String redirectScheme = "";
        if (signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_APP) {
            switch (signAccountVO.getUserType()) {
                case Constants.USER_TITLE_DR:
                    redirectScheme = "&esignAppScheme=" + ConfigUtil.getString("esign.wukong.sdk.schema.doctor");
                    break;
                case Constants.USER_TITLE_PH:
                    redirectScheme = "&esignAppScheme=" + ConfigUtil.getString("esign.wukong.sdk.schema.pharmacist");
                    break;
                default:
                    break;
            }
            param.put("redirectUrl", ConfigUtil.getString("ehp.domain.doctorH5") + ESignProperties.FACE_REDIRECT_URL
                + "?i=" + System.currentTimeMillis() + redirectScheme);
            param.put("from", "app");
        } else if (signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_CMS) {
            param.put("redirectUrl", ConfigUtil.getString("ehp.domain.cmsH5") + ESignProperties.FACE_REDIRECT_CMS_URL
                + "?i=" + System.currentTimeMillis());
            param.put("isPCRedirect", true);
        } else if (signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_PC) {
            param.put("redirectUrl", ConfigUtil.getString("ehp.domain.pcH5") + ESignProperties.FACE_REDIRECT_PC_URL
                + "?i=" + System.currentTimeMillis() + "&type=" + signParamVO.getSignFileType());
            param.put("isPCRedirect", true);
        }
        //默认签署方式
        if (StringUtils.isNotBlank(signAccountVO.getWillType())) {
            param.put("willDefaultType", signAccountVO.getWillType());
        }
        //签署方式集合，不指定时包含全部

        param.put("willTypes", EsignWillTypeEnum.getValuesByService(EsignServiceProviderEnum.ESIGN_WUKONG_SDK.getServiceId()));

        //查看该手机号是否缓存新密码，有则同步签名密码，且只能同步一次
        String cachePwd = RedisUtil.valueOps().getString(Constants.ESIGN_PWD_CACHE_PRE + signAccountVO.getPhone());
        if (StringUtils.isNotBlank(cachePwd)) {
            param.put("signPwd", cachePwd);
        }

        ResponseEntity<SignResponseVO<SignFaceResultVO>> responseEntity = RestHttpUtil.post()
            .url(ESignProperties.FACE_API)
            .header(header).body(JSONObject.toJSONString(param))
                                                                                      .execute(
                                                                                              new ParameterizedTypeReference<SignResponseVO<SignFaceResultVO>>() {
                                                                                              });

        SignResponseVO<SignFaceResultVO> body = responseEntity.getBody();

        if (body == null) {
            log.warn("获取人脸识别地址失败，结果：{}", body);
            throw new BusinessException(ExceptionCodes.ESIGN_AUTH_REQUEST_FAILED, "获取人脸识别地址失败" );
        }
        log.info("获取人脸地址：body={}", body.toString());
        //密码同步完成后及时清除
        RedisUtil.keyOps().delete(Constants.ESIGN_PWD_CACHE_PRE + signAccountVO.getPhone());
        return body.getData();
    }

    /**
     * <pre>
     *     对个人运营商三要素信息进行核验，成功后向手机号发送验证码
     *     三要素：姓名、身份证号、手机号
     * </pre>
     *
     * @param name
     */
    public String realnameByCode(String name, String phone, String idNo) throws BusinessException {
        Map<String, String> header = this.getHeaderParam();

        Map<String, String> param = new HashMap<>();
        param.put("idNo", idNo);
        param.put("name", name);
        param.put("mobileNo", phone);

        ResponseEntity<SignResponseVO<RealnameCodeResultVO>> responseEntity = RestHttpUtil.post()
                                                                                          .url(ESignProperties.REALNAME_CODE_API)
                                                                                          .header(header).body(JSONObject.toJSONString(param))
                                                                                          .execute(
                                                                                                  new ParameterizedTypeReference<SignResponseVO<RealnameCodeResultVO>>() {
                                                                                                  });

        SignResponseVO<RealnameCodeResultVO> body = responseEntity.getBody();
        log.info("获取实名认证验证码：body={}", body.toString());
        if (body == null) {
            throw new BusinessException(ExceptionCodes.ESIGN_AUTH_REQUEST_FAILED, "获取实名认证验证码失败，请联系管理员");
        }

        if (0 != body.getCode()) {
            log.info("实名认证不通过 code: {}, msg: {}", body.getCode(), body.getMessage());
            // 三要素检查不通过code
            if (ExceptionCodes.ESIGN_RESULT_NAME_PHONE_IDCARD_FAILED == body.getCode()) {
                throw new BusinessException(ExceptionCodes.ESIGN_AUTH_REQUEST_CHECK_FAILED);
            }
            throw new BusinessException(ExceptionCodes.ESIGN_REALNAME_AUTH_FAIL, body.getMessage());
        }
        String flowId = body.getData().getFlowId();
        return flowId;
    }

    /**
     * <pre>
     *     对个人运营商三要素信息进行核验，成功后向手机号发送验证码
     *     三要素：姓名、身份证号、手机号
     * </pre>
     *
     * @param flowId
     */
    public boolean checkRealnameCode(String flowId, String code) throws BusinessException {
        Map<String, String> header = this.getHeaderParam();

        Map<String, String> param = new HashMap<>();
        param.put("flowId", flowId);
        param.put("authcode", code);

        ResponseEntity<SignResponseVO> responseEntity = RestHttpUtil.put()
                                                                    .url(String.format(ESignProperties.REALNAME_CHECK_API, flowId))
                                                                    .header(header).body(JSONObject.toJSONString(param))
                                                                    .execute(new ParameterizedTypeReference<SignResponseVO>() {
                                                                    });

        SignResponseVO body = responseEntity.getBody();
        if (body == null) {
            throw new BusinessException(ExceptionCodes.ESIGN_REALNAME_AUTH_FAIL, "实名认证失败，请联系管理员");
        }
        log.info("实名认证验证结果：body={}", body.toString());
        if (0 != body.getCode()) {
            log.info("实名认证不通过 code: {}, msg: {}", body.getCode(), body.getMessage());
            // 三要素检查不通过code
            if (ExceptionCodes.ESIGN_RESULT_NAME_PHONE_IDCARD_FAILED == body.getCode()) {
                throw new BusinessException(ExceptionCodes.ESIGN_AUTH_REQUEST_CHECK_FAILED);
            }
            throw new BusinessException(ExceptionCodes.ESIGN_REALNAME_AUTH_FAIL, body.getMessage());
        }

        return true;
    }


    private Map<String, String> getHeaderParam() throws BusinessException {
        Map<String, String> header = new HashMap<>();
        header.put("X-Tsign-Open-App-Id", ESignProperties.PROJECT_ID);
        header.put("X-Tsign-Open-Token", this.accessToken());
        header.put("Content-Type", "application/json");
        return header;
    }


}
